#! /usr/bin/env python3

import json
from datetime import datetime
from time import strftime

from gai.v2.base import LoggingEnabled
from gai.v2.unify.dtype import Dataset
from gai.v2.utils import get_global_log_path, get_session_id, get_application_id, get_spark_user_name
from gai.v2.utils.logging import init_logger

logger = init_logger(__name__, get_global_log_path(), log_file_name=strftime("%Y-%m-%d") + '.log')


class PointMessage(object):
    def __init__(self):
        self.user_name = None
        self.session_id = None
        self.application_id = None
        self.actuator_module = None
        self.actuator_name = None
        self.kernel_module = None
        self.kernel_name = None
        self.instance_name = None
        self.entry = None
        self.exit = None
        self.data_size = None
        self.timestamp = None
        self.extra_info = None

    def __repr__(self):
        return str(self.__dict__)


class JointMessage(object):
    def __init__(self, entry_log: PointMessage, exit_log: PointMessage):
        assert entry_log.user_name == exit_log.user_name
        assert entry_log.session_id == exit_log.session_id
        assert entry_log.application_id == exit_log.application_id
        assert entry_log.actuator_module == exit_log.actuator_module
        assert entry_log.actuator_name == exit_log.actuator_name
        assert entry_log.kernel_module == exit_log.kernel_module
        assert entry_log.kernel_name == exit_log.kernel_name
        assert entry_log.instance_name == exit_log.instance_name
        assert entry_log.entry and exit_log.exit

        self.user_name = entry_log.user_name
        self.session_id = entry_log.session_id
        self.application_id = entry_log.application_id
        self.actuator_module = entry_log.actuator_module
        self.actuator_name = entry_log.actuator_name
        self.kernel_module = entry_log.kernel_module
        self.kernel_name = entry_log.kernel_name
        self.instance_name = entry_log.instance_name

        self.entry_timestamp = entry_log.timestamp
        self.entry_data_size = entry_log.data_size
        self.entry_extra_info = entry_log.extra_info

        self.exit_timestamp = exit_log.timestamp
        self.exit_data_size = exit_log.data_size
        self.exit_extra_info = exit_log.extra_info

    def __repr__(self):
        return str(self.__dict__)


class StatLogger(object):

    def __new__(cls):
        """

        Returns:
            a singleton instance of ``StatLogger``

        See Also:
            Chapter 2, Learning Python Design Patterns, by Gennadiy Zlobin
        """

        if not hasattr(cls, 'instance'):
            cls.instance = super(StatLogger, cls).__new__(cls)

            cls.instance.user_name = get_spark_user_name()
            cls.instance.session_id = get_session_id()
            cls.instance.application_id = get_application_id()

        return cls.instance

    @staticmethod
    def get_instance():
        """

        Returns:
            a singleton instance of <code>StatLogger</code>

        Notes:
            There is no need for this method. To follow the convention
            of singleton pattern, we keep this one.
        """
        return StatLogger()

    def _prepare_message(self, actuator, dataset):
        msg = PointMessage()
        msg.user_name = self.user_name
        msg.session_id = self.session_id
        msg.application_id = self.application_id
        msg.kernel_module = actuator.getKernelModule()
        msg.kernel_name = actuator.getKernelName()
        msg.actuator_module = actuator.__class__.__module__
        msg.actuator_name = actuator.__class__.__name__
        msg.instance_name = actuator.getInstanceName()
        msg.timestamp = str(datetime.now())
        msg.data_size = "Unavailable"
        msg.extra_info = actuator.getExtraInfo()
        return msg

    def pre_hook(self, actuator: LoggingEnabled, dataset: Dataset):
        """ Output an entry log.

        Args:
            actuator: the actuator to be logged
            dataset: the dataset to be logged
        """
        pre_msg = self._prepare_message(actuator, dataset)
        pre_msg.entry = True
        pre_msg.exit = False
        actuator._push(pre_msg)
        self.log({"entry": pre_msg.__dict__})

    def post_hook(self, actuator: LoggingEnabled, dataset: Dataset):
        """Output a joint log with both entry and exit information

        Args:
            actuator: the actuator to be logged
            dataset: the dataset to be logged
        """
        pre_msg = actuator._pop()
        post_msg = self._prepare_message(actuator, dataset)
        post_msg.entry = False
        post_msg.exit = True
        pre_post_msg = JointMessage(pre_msg, post_msg)
        self.log({"entry_exit": pre_post_msg.__dict__})

    def log(self, message):
        """Outputs to the log stream.

        Args:
            message: the object to output in the log stream
        """
        json_str = json.dumps(message, separators=(",", ":"))
        logger.warning(json_str[1:-1])
